{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xLOXFOT5Q40E"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "iiQkM5ZgQ8r2"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "j6331ZSsQGY3"
      },
      "source": [
        "# 그래디언트 계산하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "i9Jcnb8bQQyd"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/quantum/tutorials/gradients\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/quantum/blob/master/docs/tutorials/gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/quantum/blob/master/docs/tutorials/gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/quantum/docs/tutorials/gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FxkQA6oblNqI"
      },
      "source": [
        "이 튜토리얼에서는 양자 회로의 기대 값에 대한 그래디언트 계산 알고리즘을 탐색합니다.\n",
        "\n",
        "양자 회로에서 관찰 가능한 특정 기대 값의 그래디언트를 계산하는 것은 복잡한 프로세스입니다. 관찰 가능 항목의 기대 값은 기록하기 쉬운 분석적 그래디언트 수식이 있는 행렬 곱셈 또는 벡터 더하기와 같은 기존의 머신러닝 변환과 달리 언제든 기록하기 쉬운 분석적 그래디언트 수식을 사용할 수 없습니다. 결과적으로 다양한 시나리오에 유용한 다양한 양자 그래디언트 계산 방법이 있습니다. 이 튜토리얼에서는 두 가지의 다른 미분 체계를 비교하고 대조합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pvG0gAJqGYJo"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TorxE5tnkvb2"
      },
      "outputs": [],
      "source": [
        "!pip install tensorflow==2.1.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OIbP5hklC338"
      },
      "source": [
        "TensorFlow Quantum을 설치하세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "saFHsRDpkvkH"
      },
      "outputs": [],
      "source": [
        "!pip install tensorflow-quantum"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MkTqyoSxGUfB"
      },
      "source": [
        "이제 TensorFlow 및 모듈 종속성을 가져옵니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "enZ300Bflq80"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "import tensorflow_quantum as tfq\n",
        "\n",
        "import cirq\n",
        "import sympy\n",
        "import numpy as np\n",
        "\n",
        "# visualization tools\n",
        "%matplotlib inline\n",
        "import matplotlib.pyplot as plt\n",
        "from cirq.contrib.svg import SVGCircuit"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b08Mmbs8lr81"
      },
      "source": [
        "## 1. 준비\n",
        "\n",
        "양자 회로에 대한 그래디언트 계산 개념을 좀 더 구체적으로 만들어 보겠습니다. 다음과 같은 매개변수화된 회로가 있다고 가정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YkPYJ_Ak-GKu"
      },
      "outputs": [],
      "source": [
        "qubit = cirq.GridQubit(0, 0)\n",
        "my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha'))\n",
        "SVGCircuit(my_circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wgQIlCWy-MVr"
      },
      "source": [
        "관찰 가능 항목과 함께:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xurmJdFy-Jae"
      },
      "outputs": [],
      "source": [
        "pauli_x = cirq.X(qubit)\n",
        "pauli_x"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "j3OzKYe5NT_W"
      },
      "source": [
        "이 연산자를 보면 $⟨Y(\\alpha)| X | Y(\\alpha)⟩ = \\sin(\\pi \\ alpha)$라는 것을 알 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ps-pd2mndXs7"
      },
      "outputs": [],
      "source": [
        "def my_expectation(op, alpha):\n",
        "    \"\"\"Compute ⟨Y(alpha)| `op` | Y(alpha)⟩\"\"\"\n",
        "    params = {'alpha': alpha}\n",
        "    sim = cirq.Simulator()\n",
        "    final_state = sim.simulate(my_circuit, params).final_state\n",
        "    return op.expectation_from_wavefunction(final_state, {qubit: 0}).real\n",
        "\n",
        "\n",
        "my_alpha = 0.3\n",
        "print(\"Expectation=\", my_expectation(pauli_x, my_alpha))\n",
        "print(\"Sin Formula=\", np.sin(np.pi * my_alpha))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zcCX109cJUaz"
      },
      "source": [
        "$f_{1}(\\alpha) = ⟨Y(\\alpha)| X | Y(\\alpha)⟩$를 정의하면 $f_{1}^{'}(\\alpha) = \\pi \\cos(\\pi \\alpha)$입니다. 확인해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VMq7EayNRyQb"
      },
      "outputs": [],
      "source": [
        "def my_grad(obs, alpha, eps=0.01):\n",
        "    grad = 0\n",
        "    f_x = my_expectation(obs, alpha)\n",
        "    f_x_prime = my_expectation(obs, alpha + eps)\n",
        "    return ((f_x_prime - f_x) / eps).real\n",
        "\n",
        "\n",
        "print('Finite difference:', my_grad(pauli_x, my_alpha))\n",
        "print('Cosine formula:   ', np.pi * np.cos(np.pi * my_alpha))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-SUlLpXBeicF"
      },
      "source": [
        "## 2. 미분기의 필요성\n",
        "\n",
        "더 큰 회로일수록 주어진 양자 회로의 그래디언트를 정확하게 계산하는 공식이 항상 주어지지 않습니다. 간단한 공식으로 그래디언트를 계산하기에 충분하지 않은 경우 `tfq.differentiators.Differentiator` 클래스를 사용하여 회로의 그래디언트를 계산하기 위한 알고리즘을 정의할 수 있습니다. 예를 들어, 다음을 사용하여 TensorFlow Quantum(TFQ)의 상기 예를 다시 재현할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Om76ZLu8NT_i"
      },
      "outputs": [],
      "source": [
        "expectation_calculation = tfq.layers.Expectation(\n",
        "    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
        "\n",
        "expectation_calculation(my_circuit,\n",
        "                        operators=pauli_x,\n",
        "                        symbol_names=['alpha'],\n",
        "                        symbol_values=[[my_alpha]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lx3y2DX9NT_k"
      },
      "source": [
        "그러나 샘플링을 기반으로 예상치로 전환하면(실제 기기에서 발생하는 일) 값이 약간 변경될 수 있습니다. 이것은 이제 불완전한 추정치를 가지고 있음을 의미합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v27rRyAHNT_l"
      },
      "outputs": [],
      "source": [
        "sampled_expectation_calculation = tfq.layers.SampledExpectation(\n",
        "    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
        "\n",
        "sampled_expectation_calculation(my_circuit,\n",
        "                                operators=pauli_x,\n",
        "                                repetitions=500,\n",
        "                                symbol_names=['alpha'],\n",
        "                                symbol_values=[[my_alpha]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Igwa3EnzNT_p"
      },
      "source": [
        "이것은 그래디언트와 관련하여 심각한 정확성 문제로 빠르게 복합화될 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "StljXH38NT_q"
      },
      "outputs": [],
      "source": [
        "# Make input_points = [batch_size, 1] array.\n",
        "input_points = np.linspace(0, 5, 200)[:, np.newaxis].astype(np.float32)\n",
        "exact_outputs = expectation_calculation(my_circuit,\n",
        "                                        operators=pauli_x,\n",
        "                                        symbol_names=['alpha'],\n",
        "                                        symbol_values=input_points)\n",
        "imperfect_outputs = sampled_expectation_calculation(my_circuit,\n",
        "                                                    operators=pauli_x,\n",
        "                                                    repetitions=500,\n",
        "                                                    symbol_names=['alpha'],\n",
        "                                                    symbol_values=input_points)\n",
        "plt.title('Forward Pass Values')\n",
        "plt.xlabel('$x$')\n",
        "plt.ylabel('$f(x)$')\n",
        "plt.plot(input_points, exact_outputs, label='Analytic')\n",
        "plt.plot(input_points, imperfect_outputs, label='Sampled')\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dfXObk7KNT_t"
      },
      "outputs": [],
      "source": [
        "# Gradients are a much different story.\n",
        "values_tensor = tf.convert_to_tensor(input_points)\n",
        "\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(values_tensor)\n",
        "    exact_outputs = expectation_calculation(my_circuit,\n",
        "                                            operators=pauli_x,\n",
        "                                            symbol_names=['alpha'],\n",
        "                                            symbol_values=values_tensor)\n",
        "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
        "\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(values_tensor)\n",
        "    imperfect_outputs = sampled_expectation_calculation(\n",
        "        my_circuit,\n",
        "        operators=pauli_x,\n",
        "        repetitions=500,\n",
        "        symbol_names=['alpha'],\n",
        "        symbol_values=values_tensor)\n",
        "sampled_finite_diff_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
        "\n",
        "plt.title('Gradient Values')\n",
        "plt.xlabel('$x$')\n",
        "plt.ylabel('$f^{\\'}(x)$')\n",
        "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
        "plt.plot(input_points, sampled_finite_diff_gradients, label='Sampled')\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ld34TJvTNT_w"
      },
      "source": [
        "여기서 유한 차분 공식은 분석 사례에서 그래디언트를 계산하는 것이 빠르지만 샘플링 기반 방법의 경우 노이즈가 너무 많습니다. 좋은 그래디언트를 계산할 수 있도록 보다 신중한 기술을 사용해야 합니다. 다음으로 분석적 기대 그래디언트 계산에는 적합하지 않지만 실제 샘플 기반 사례에서 훨씬 더 성능을 발휘하는 훨씬 느린 기술을 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JsBxH_RaNT_x"
      },
      "outputs": [],
      "source": [
        "# A smarter differentiation scheme.\n",
        "gradient_safe_sampled_expectation = tfq.layers.SampledExpectation(\n",
        "    differentiator=tfq.differentiators.ParameterShift())\n",
        "\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(values_tensor)\n",
        "    imperfect_outputs = gradient_safe_sampled_expectation(\n",
        "        my_circuit,\n",
        "        operators=pauli_x,\n",
        "        repetitions=500,\n",
        "        symbol_names=['alpha'],\n",
        "        symbol_values=values_tensor)\n",
        "\n",
        "sampled_param_shift_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
        "\n",
        "plt.title('Gradient Values')\n",
        "plt.xlabel('$x$')\n",
        "plt.ylabel('$f^{\\'}(x)$')\n",
        "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
        "plt.plot(input_points, sampled_param_shift_gradients, label='Sampled')\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0xlUlh8wNT_z"
      },
      "source": [
        "위에서 특정 연구 시나리오에 특정 미분기가 가장 잘 사용됨을 알 수 있습니다. 일반적으로 기기 노이즈 등에 강한 느린 샘플 기반 방법은 보다 '실제' 설정에서 알고리즘을 테스트하거나 구현할 때 유용한 미분기입니다. 유한 차분과 같은 더 빠른 방법은 분석 계산 및 더 높은 처리량을 원하지만 아직 알고리즘의 기기 실행 가능성에 관심이 없는 경우 적합합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FaijzZ4MNT_0"
      },
      "source": [
        "## 3. 다중 observable\n",
        "\n",
        "두 번째 observable을 소개하고 TensorFlow Quantum이 단일 회로에 대해 여러 observable을 지원하는 방법을 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ytgB_DqDNT_3"
      },
      "outputs": [],
      "source": [
        "pauli_z = cirq.Z(qubit)\n",
        "pauli_z"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r51TZls4NT_6"
      },
      "source": [
        "이 observable이 이전과 같은 회로에서 사용된다면 $f_{2}(\\alpha) = ⟨Y(\\alpha)| Z | Y (\\alpha)⟩ = \\cos(\\pi \\alpha)$ 및 $f_{2}^{'}(\\alpha) = -\\pi \\sin (\\pi \\alpha)$입니다. 간단하게 확인해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "19FKgu0ANT_7"
      },
      "outputs": [],
      "source": [
        "test_value = 0.\n",
        "\n",
        "print('Finite difference:', my_grad(pauli_z, test_value))\n",
        "print('Sin formula:      ', -np.pi * np.sin(np.pi * test_value))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_33Y5mL0NT_-"
      },
      "source": [
        "이 정도면 일치한다고 볼 수 있습니다.\n",
        "\n",
        "이제 $g(\\alpha) = f_{1}(\\alpha) + f_{2}(\\alpha)$를 정의하면 $g'(\\alpha) = f_{1}^{'}(\\alpha) + f^{'}_{2}(\\alpha)$입니다. 회로와 함께 사용하기 위해 TensorFlow Quantum에서 하나 이상의 observable을 정의하는 것은 $g$에 더 많은 용어를 추가하는 것과 같습니다.\n",
        "\n",
        "이것은 회로에서 특정 심볼의 그래디언트가 해당 회로에 적용된 해당 심볼의 각 observable에 대해 그래디언트의 합과 동일함을 의미합니다. 이는 TensorFlow 그래디언트 가져오기 및 역전파(특정 심볼에 대한 그래디언트로 모든 observable에 대한 그래디언트 합계를 제공)와 호환됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3WFJfFEbNT_-"
      },
      "outputs": [],
      "source": [
        "sum_of_outputs = tfq.layers.Expectation(\n",
        "    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
        "\n",
        "sum_of_outputs(my_circuit,\n",
        "               operators=[pauli_x, pauli_z],\n",
        "               symbol_names=['alpha'],\n",
        "               symbol_values=[[test_value]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-ujQUu3WNUAB"
      },
      "source": [
        "여기서 첫 번째 항목은 예상 w.r.t Pauli X이고, 두 번째 항목은 예상 w.r.t Pauli Z입니다. 그래디언트를 사용할 때는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jcAQa9l0NUAB"
      },
      "outputs": [],
      "source": [
        "test_value_tensor = tf.convert_to_tensor([[test_value]])\n",
        "\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(test_value_tensor)\n",
        "    outputs = sum_of_outputs(my_circuit,\n",
        "                             operators=[pauli_x, pauli_z],\n",
        "                             symbol_names=['alpha'],\n",
        "                             symbol_values=test_value_tensor)\n",
        "\n",
        "sum_of_gradients = g.gradient(outputs, test_value_tensor)\n",
        "\n",
        "print(my_grad(pauli_x, test_value) + my_grad(pauli_z, test_value))\n",
        "print(sum_of_gradients.numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-fZmbYGANUAE"
      },
      "source": [
        "여기에서 각 observable의 그래디언트의 합이 실제로 $\\alpha$의 그래디언트임을 확인했습니다. 이 동작은 모든 TensorFlow Quantum 미분기에서 지원하며 나머지 TensorFlow와의 호환성에 중요한 역할을 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lZsGG7lWNUAF"
      },
      "source": [
        "## 4. 고급 사용법\n",
        "\n",
        "여기서는 양자 회로에 대한 사용자 정의 미분 루틴을 정의하는 방법을 배웁니다. TensorFlow Quantum 서브 클래스 `tfq.differentiators.Differentiator` 내에 존재하는 모든 미분기입니다. 미분기에서 `differentiate_analytic ` 및 `differentiate_sampled`를 구현해야 합니다.\n",
        "\n",
        "다음은 TensorFlow Quantum 구조를 사용하여 이 튜토리얼의 첫 번째 부분에 나온 폐쇄형 솔루션을 구현합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5iY4q6FKNUAG"
      },
      "outputs": [],
      "source": [
        "class MyDifferentiator(tfq.differentiators.Differentiator):\n",
        "    \"\"\"A Toy differentiator for <Y^alpha | X |Y^alpha>.\"\"\"\n",
        "\n",
        "    def __init__(self):\n",
        "        pass\n",
        "\n",
        "    @tf.function\n",
        "    def _compute_gradient(self, symbol_values):\n",
        "        \"\"\"Compute the gradient based on symbol_values.\"\"\"\n",
        "\n",
        "        # f(x) = sin(pi * x)\n",
        "        # f'(x) = pi * cos(pi * x)\n",
        "        return tf.cast(tf.cos(symbol_values * np.pi) * np.pi, tf.float32)\n",
        "\n",
        "    @tf.function\n",
        "    def differentiate_analytic(self, programs, symbol_names, symbol_values,\n",
        "                               pauli_sums, forward_pass_vals, grad):\n",
        "        \"\"\"Specify how to differentiate a circuit with analytical expectation.\n",
        "\n",
        "        This is called at graph runtime by TensorFlow. `differentiate_analytic`\n",
        "        should calculate the gradient of a batch of circuits and return it\n",
        "        formatted as indicated below. See\n",
        "        `tfq.differentiators.ForwardDifference` for an example.\n",
        "\n",
        "        Args:\n",
        "            programs: `tf.Tensor` of strings with shape [batch_size] containing\n",
        "                the string representations of the circuits to be executed.\n",
        "            symbol_names: `tf.Tensor` of strings with shape [n_params], which\n",
        "                is used to specify the order in which the values in\n",
        "                `symbol_values` should be placed inside of the circuits in\n",
        "                `programs`.\n",
        "            symbol_values: `tf.Tensor` of real numbers with shape\n",
        "                [batch_size, n_params] specifying parameter values to resolve\n",
        "                into the circuits specified by programs, following the ordering\n",
        "                dictated by `symbol_names`.\n",
        "            pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops]\n",
        "                containing the string representation of the operators that will\n",
        "                be used on all of the circuits in the expectation calculations.\n",
        "            forward_pass_vals: `tf.Tensor` of real numbers with shape\n",
        "                [batch_size, n_ops] containing the output of the forward pass\n",
        "                through the op you are differentiating.\n",
        "            grad: `tf.Tensor` of real numbers with shape [batch_size, n_ops]\n",
        "                representing the gradient backpropagated to the output of the\n",
        "                op you are differentiating through.\n",
        "\n",
        "        Returns:\n",
        "            A `tf.Tensor` with the same shape as `symbol_values` representing\n",
        "            the gradient backpropagated to the `symbol_values` input of the op\n",
        "            you are differentiating through.\n",
        "        \"\"\"\n",
        "\n",
        "        # Computing gradients just based off of symbol_values.\n",
        "        return self._compute_gradient(symbol_values) * grad\n",
        "\n",
        "    @tf.function\n",
        "    def differentiate_sampled(self, programs, symbol_names, symbol_values,\n",
        "                              pauli_sums, num_samples, forward_pass_vals, grad):\n",
        "        \"\"\"Specify how to differentiate a circuit with sampled expectation.\n",
        "\n",
        "        This is called at graph runtime by TensorFlow. `differentiate_sampled`\n",
        "        should calculate the gradient of a batch of circuits and return it\n",
        "        formatted as indicated below. See\n",
        "        `tfq.differentiators.ForwardDifference` for an example.\n",
        "\n",
        "        Args:\n",
        "            programs: `tf.Tensor` of strings with shape [batch_size] containing\n",
        "                the string representations of the circuits to be executed.\n",
        "            symbol_names: `tf.Tensor` of strings with shape [n_params], which\n",
        "                is used to specify the order in which the values in\n",
        "                `symbol_values` should be placed inside of the circuits in\n",
        "                `programs`.\n",
        "            symbol_values: `tf.Tensor` of real numbers with shape\n",
        "                [batch_size, n_params] specifying parameter values to resolve\n",
        "                into the circuits specified by programs, following the ordering\n",
        "                dictated by `symbol_names`.\n",
        "            pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops]\n",
        "                containing the string representation of the operators that will\n",
        "                be used on all of the circuits in the expectation calculations.\n",
        "            num_samples: `tf.Tensor` of positive integers representing the\n",
        "                number of samples per term in each term of pauli_sums used\n",
        "                during the forward pass.\n",
        "            forward_pass_vals: `tf.Tensor` of real numbers with shape\n",
        "                [batch_size, n_ops] containing the output of the forward pass\n",
        "                through the op you are differentiating.\n",
        "            grad: `tf.Tensor` of real numbers with shape [batch_size, n_ops]\n",
        "                representing the gradient backpropagated to the output of the\n",
        "                op you are differentiating through.\n",
        "\n",
        "        Returns:\n",
        "            A `tf.Tensor` with the same shape as `symbol_values` representing\n",
        "            the gradient backpropagated to the `symbol_values` input of the op\n",
        "            you are differentiating through.\n",
        "        \"\"\"\n",
        "        return self._compute_gradient(symbol_values) * grad"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bvEgw2m6NUAI"
      },
      "source": [
        "이 새로운 미분기는 이제 기존 `tfq.layer` 객체와 함께 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QrKnkWswNUAJ"
      },
      "outputs": [],
      "source": [
        "custom_dif = MyDifferentiator()\n",
        "custom_grad_expectation = tfq.layers.Expectation(differentiator=custom_dif)\n",
        "\n",
        "# Now let's get the gradients with finite diff.\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(values_tensor)\n",
        "    exact_outputs = expectation_calculation(my_circuit,\n",
        "                                            operators=[pauli_x],\n",
        "                                            symbol_names=['alpha'],\n",
        "                                            symbol_values=values_tensor)\n",
        "\n",
        "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
        "\n",
        "# Now let's get the gradients with custom diff.\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(values_tensor)\n",
        "    my_outputs = custom_grad_expectation(my_circuit,\n",
        "                                         operators=[pauli_x],\n",
        "                                         symbol_names=['alpha'],\n",
        "                                         symbol_values=values_tensor)\n",
        "\n",
        "my_gradients = g.gradient(my_outputs, values_tensor)\n",
        "\n",
        "plt.subplot(1, 2, 1)\n",
        "plt.title('Exact Gradient')\n",
        "plt.plot(input_points, analytic_finite_diff_gradients.numpy())\n",
        "plt.xlabel('x')\n",
        "plt.ylabel('f(x)')\n",
        "plt.subplot(1, 2, 2)\n",
        "plt.title('My Gradient')\n",
        "plt.plot(input_points, my_gradients.numpy())\n",
        "plt.xlabel('x')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oXqcJWigNUAL"
      },
      "source": [
        "이제 이 새로운 미분기를 사용하여 미분 ops를 생성할 수 있습니다.\n",
        "\n",
        "요점: 차별화 요소는 한 번에 하나의 op에만 연결할 수 있으므로 이전 op에 연결된 미분기는 새 op에 연결하기 전에 새로 고쳐야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F_WHcj3bNUAM"
      },
      "outputs": [],
      "source": [
        "# Create a noisy sample based expectation op.\n",
        "expectation_sampled = tfq.get_sampled_expectation_op(\n",
        "    cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.01)))\n",
        "\n",
        "# Make it differentiable with your differentiator:\n",
        "# Remember to refresh the differentiator before attaching the new op\n",
        "custom_dif.refresh()\n",
        "differentiable_op = custom_dif.generate_differentiable_op(\n",
        "    sampled_op=expectation_sampled)\n",
        "\n",
        "# Prep op inputs.\n",
        "circuit_tensor = tfq.convert_to_tensor([my_circuit])\n",
        "op_tensor = tfq.convert_to_tensor([[pauli_x]])\n",
        "single_value = tf.convert_to_tensor([[my_alpha]])\n",
        "num_samples_tensor = tf.convert_to_tensor([[1000]])\n",
        "\n",
        "with tf.GradientTape() as g:\n",
        "    g.watch(single_value)\n",
        "    forward_output = differentiable_op(circuit_tensor, ['alpha'], single_value,\n",
        "                                       op_tensor, num_samples_tensor)\n",
        "\n",
        "my_gradients = g.gradient(forward_output, single_value)\n",
        "\n",
        "print('---TFQ---')\n",
        "print('Foward:  ', forward_output.numpy())\n",
        "print('Gradient:', my_gradients.numpy())\n",
        "print('---Original---')\n",
        "print('Forward: ', my_expectation(pauli_x, my_alpha))\n",
        "print('Gradient:', my_grad(pauli_x, my_alpha))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OGWcpqzDNUAP"
      },
      "source": [
        "성공: 이제 TensorFlow Quantum이 제공하는 모든 미분기를 사용하고 자신만의 미분기를 정의할 수 있습니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "gradients.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
